Skip to content

feat: add Interval Test and Focus Test generators to the Tools menu - #539

Open
switch87 wants to merge 29 commits into
barebaric:mainfrom
switch87:feat/test-generators
Open

switch87 wants to merge 29 commits into
barebaric:mainfrom
switch87:feat/test-generators

Conversation

@switch87

@switch87 switch87 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR change?

The Material Test Grid, the Interval Test and the Focus Test now work the same way and share one generator architecture, as you asked in your review of 15/10. This builds on your workpiece filter from 5892810.

  • Tools → Create … adds the test with default settings on a new layer named after the test, and selects it. There is no dialog. The settings show under Test Settings in the item properties panel, and the panel scrolls to them, so the test stays visible on the canvas.
  • A change in Test Settings rebuilds the test as one undo step. The test keeps its bottom-left corner, and moves back onto the work area if it grows past the edge. Operations the user adds to the layer stay.
  • Material Test Grid: presets, grid, labels and ranges moved from its step settings to Test Settings. The step keeps its own settings (laser head, post-processing) when the grid is rebuilt. A recipe applied to the step resizes the grid and shows in Test Settings. The G-code is the same as before; a test compares it with a grid built the old way. A grid from an older project can be changed the same way.
  • Interval Test: a row of filled squares, each with its own Engrave step and line interval, at one power and speed, labelled in mm and LPI.
  • Focus Test: one line per focus offset, with Z axis steps (relative G91/G0 Z/G90, back to the start height at the end), Manual (M0 before each line) or Ramp (one long line with 10 mm ticks). A new Focus Test uses the first method the machine supports (Focus Test: default the Method to "Z axis steps" when the machine has a Z axis #550). A test made with Z axis steps keeps that method on a machine without a Z axis, and Test Settings say it is not available there.
  • Deleting a test's layer also removes its saved settings; undo brings them back.
  • Docs updated in all languages. New screenshot targets: material-test, interval-test, focus-test. The docs have new screenshots from those targets.

Fixes #519. Closes #550.

Why did you choose this specific architectural approach?

The pipeline only sees Step.workpiece_uids. Everything else is in laser_essentials:

  • calibration/generator.py: a CalibrationGenerator turns parameters into a layout. It does not touch the document or GTK.
  • calibration/layout.py: a layout is an ordered list of cells (one shape plus the step that processes only that shape) and machine commands between them. List order is run order.
  • calibration/builder.py: makes the workpieces, steps and source assets for a layout, in test coordinates, and moves the whole test into place.
  • commands/calibration_cmd.py: create() and update_params() are the only code that changes the document. A rebuild swaps the old items for the new ones in bulk with the pipeline paused, like grouping does.
  • calibration/record.py: a hidden asset stores the generator, the parameters and what was generated. It is saved in the .ryp.
  • widgets/calibration_provider.py: the Test Settings are a property provider, like the sketch parameters. GTK4 can't place a window on Wayland, so a window could still cover the test. The panel never does.

In core there is one fix: Workflow.add_children/remove_children skipped the wiring for per-step post-processor changes. Doc.remove_asset_by_uid also got a silent flag, like add_asset.

You didn't answer the open questions from the phase 2 plan, so I made these choices. Tell me if you want them different:

  1. The grid stays one assembler step; per-cell steps would mean up to 400 steps.
  2. Parameters are kept in a hidden asset, not on Layer.
  3. A change rebuilds the Interval and Focus Test operations, so edits made to them by hand are replaced. The panel says so.
  4. The rest is as in the plan.

No .po/.pot changes and no changelog entry.

How did you manually test this locally?

I ran the app in a separate headless KWin session with tests/config, once with and once without a Z axis on the machine:

  1. Material Test Grid: a new layer, the grid selected, Test Settings in view. I set 3×4 cells and the grid shrank from 73×73 to 49×61 mm on the same corner. Speed vs Offset filled in Engrave and a 0.5 mm line interval. Undo went back one step.
  2. Interval Test: 7 cells gave 8 workpieces, still selected. A smallest interval above the largest shows an error and leaves the test alone.
  3. Focus Test without Z axis: Manual, with a pause before each line. Switching to Ramp gave one line with ticks. With Z axis: it started on Z axis steps, 5 lines gave 12 steps, and undo restored 20.
  4. No window opened in any of these steps.

Automated: end-to-end tests in tests/doceditor/test_calibration_tests.py (G-code goldens, grid against the old way, recipe, undo/redo, save/load, layer deletion), unit and UI tests in rayforge-addon-laser/tests, and the Tools actions in the real main window in tests/ui_gtk/doceditor/test_calibration_test_settings.py. Results as CI runs them:

  • pytest -m "not ui and not stress" --ignore=tests/ui_gtk: 7748 passed
  • pytest -m stress: 18 passed
  • pytest -m ui (headless): 1154 passed, 14 skipped (the tests that need an X display)
  • ruff, flake8, pyflakes and pyright: clean

Checklist

  • I am not an AI. I have manually read the code and understand it.
  • I take full responsibility for the quality and architectural fit of
    this code.
  • I agree to publish this code under the MIT license.

@atkaper

atkaper commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

@switch87 Nice one! I have not run it on my laser yet (might try later this week), but I did run your PR without machine to try your new function. Would it be an idea to make the Method "Z- axis steps" the default if the machine definition indicates it supports a z-axis? Of course just a nice to have, but I think it would make sense.

@switch87

Copy link
Copy Markdown
Contributor Author

@atkaper Good idea, done in 73ca5b7. The method list was already ordered by preference (Z axis steps, manual, ramp) and only contains what the machine supports, but the dialog started on the manual method regardless. It now starts on the first available method, so a machine with a Z axis and a G-code driver opens on Z axis steps. A UI test covers it, and the docs say which method is the default. Also rebased onto current main.

@atkaper

atkaper commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Super! I will try this one later this week!

@switch87

Copy link
Copy Markdown
Contributor Author

Dropped the laser_essentials .po/.pot changes from this PR, following your rule of one catalog update before each release; the new strings are still wrapped in _(). Also rebased onto current main and made the calibration tests wait for the editor to settle, since one of them left tasks behind at teardown.

@switch87

Copy link
Copy Markdown
Contributor Author

I went over this again against the rest of the code base and pushed two small follow-up commits on top, no force-push:

  • 0869c0c: the Focus Test no longer imports the private _driver_uses_gcode from the intent builder; it resolves the driver with get_driver_cls like Machine does. Added a test for a non-G-code driver.
  • 2dfbf11: the two dialogs are now declared as VarSets and rendered with VarSetWidget, like the Material Test Grid settings, instead of hand-built rows. Power is the usual percent slider, the speed limit comes from the machine, and the dialog is as wide as the step settings dialog. The label strings reuse the existing Material Test ones.

I also corrected the PR description, which described the ramp and the Interval Test wrongly.

@knipknap

knipknap commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Thank you for this PR! In testing I noticed some issues:

  1. The dialog pops up as a modal in the middle of the screen, hiding the produced test while editing.
  2. I think UX wise the test feature should be aligned with the existing Material Test Grid feature. Currently, the two behave differently: Clicking "Material Test Grid" creates a step in the workflow and a generated workpiece, without opening the dialog, whereas your new tools DO open the dialog immediately. I agree that opening the dialog is the better behavior here, but in any case, all three tests should behave the same, UX wise. I do not mind changing the approach of the Material Test Grid feature - I actually like your approach of generating a completely new layer instead, it seems more elegant than what the material test grid currently does.
  3. Code architecture wise, I think the tests/generators should be aligned as well. The new test generators appear to share little code with the existing test generator, which indicates that either the pre-existing architecture is insufficient, or the new implementation just misses opportunities for generalization.

Proposed next phases

  1. Your code exposed a pre-existing code smell that I introduced and builds on it: The pipeline currently filters on a generated_workpiece_uid - this is actually a layer violation, as it forces the pipeline to understand the "source" of the workpiece instead of using a generic approach. I would refactor this into a step.selected_workpiece_uids: List[str], removing the leak. The step settings UI could then display the list of workpieces in a new tab. (I would not make this editable in the first step.). Without this change, the UI implies a behavior that does not match reality: It shows raster steps on a layer implying all workpieces are processed (because the current UI assumes a step=full layer processing) when in reality there is a per-workpiece filter - it just isn't visible anywhere. In an ideal world, each step icon would show a tiny number if it filters on workpieces, but I am fine to move ahead without this; this could be a future improvement.
  2. Create an iterative phase 2 implementation plan for review that starts with refactoring the pre-existing test generator for re-usability with the new tests. This may also include improving the UI experience with the nice features introduced by your PR (e.g. dedicated layer for the test, per-cell editable result, UI dialog opens immediately).
  3. Implementation of the new test generators from this PR on top of the cleaned up architecture. This should also close another small gap I noticed: It could add new targets to the screenshotter so that the docs include the proper screenshots.

Specific design proposal for phase 1 (AI generated but reviewed)

  • Generalize Step.generated_workpiece_uid into a workpiece filter that the intent builder no longer treats as a special concept.
  • Expose it in the step settings dialog as a READ-ONLY page (the last tab) listing which workpieces the step processes. No interactive editing; only generators (Material Test Grid, and the Interval/Focus Tests from the current PR) set the filter programmatically.
  • No warning when a filtered step matches no workpieces (silent skip, same as today).
  1. Core model — rayforge/core/step.py
  • Replace generated_workpiece_uid: str | None (line 97) with workpiece_uids: list[str], where an empty list means "all workpieces of the layer" (current None semantics).
  • Add Step.selected_workpieces(workpieces) -> list[WorkPiece]: returns all workpieces when the filter is empty, otherwise the layer workpieces whose uid is in the filter, in layer order; missing uids are simply skipped (preserves today's silent behavior).
  • Add set_workpiece_uids(uids) setter (dedupe, normalize, updated.send(self) only on real change) so undo commands and generators have one entry point.
  • Serialization: to_dict writes workpiece_uids; _serialized_keys swaps the old key for the new; from_dict reads workpiece_uids and falls back to a legacy generated_workpiece_uid (→ [uid]) so existing .ryp projects load unchanged. Older app versions round-trip the unknown key via the existing extra mechanism.
  • The filter is intentionally NOT added to get_cache_params() or the recipe varset: the pipeline graph shape (which workpiece nodes exist) and the aggregate token's upstream key list already capture it, and it must not become a recipe-extractable process setting.
  1. Pipeline — rayforge/pipeline/intent_builder.py
  • _workpieces_for_step (line 292) becomes a delegation: return step.selected_workpieces(workpieces). The builder keeps only the generic "a step selects its workpieces" concept; the "generated" notion disappears from the pipeline (update the module docstring wording).
  1. Generators
  • laser_essentials/commands/calibration_test_cmd.py: _link sets step.workpiece_uids = [wp.uid]; update the module docstring.
  • laser_essentials/commands/material_test_cmd.py: line 127 same; sync_preview_from_step (line 169) reads the first uid of step.workpiece_uids instead of generated_workpiece_uid.
  • laser_essentials/steps/laser_step.py: get_settings() appears to be obsolete so it could be removed. Otherwise: swaps the key to workpiece_uids.
  1. UI — read-only "Workpieces" page
  • New rayforge/ui_gtk/doceditor/step_settings/pages/workpieces.py: WorkpiecesPage(StepSettingsPage) with show_identity = False (same pattern as PostProcessingPage).
    • A summary row: "All workpieces on this layer" when the filter is empty, otherwise "N of M workpieces".
    • One non-interactive Adw.ActionRow per workpiece of step.layer.all_workpieces (name; check-style icon when included, dimmed/minus icon when excluded by a filter).
    • Read-only: no switches, no checkbuttons, nothing persisted.
    • Refreshes on step.updated and on layer descendant_added/descendant_removed so the list stays current while the dialog is open.
  • dialog.py: after the Post Processing page, add the Workpieces page as the final tab — only when step.needs_workpieces and step.layer exist (hidden for CommandStep and detached steps). Cleanup hookup via the existing _cleanup path.
  1. Tests
  • tests/core/test_step.py: update the serialization tests to workpiece_uids; add a legacy-file test (from_dict with only generated_workpiece_uid yields the filter; to_dict emits only the new key).
  • tests/pipeline/test_intent_builder.py (lines ~122-140): switch to the new field; keep the "generated workpiece missing → step contributes nothing" case.
  • tests/doceditor/test_calibration_tests.py (line 123): assert the linkage via workpiece_uids.
  • New tests/ui_gtk/ test for the page: filter set → only matching workpieces shown as included; empty filter → summary row says all; command step → page not added by the dialog (logic-level check).
  1. Docs & housekeeping
  • CHANGELOG.md entry.
  • New user-visible strings wrapped in _(); follow docs/agents/translations.md for catalogs.
  • pixi run lint first, then targeted tests: pixi run test tests/core/test_step.py tests/pipeline/test_intent_builder.py tests/doceditor/test_calibration_tests.py and pixi run uitest for the new UI test.

Out of scope (deferred by design): interactive filter editing in the UI, multi-select semantics beyond what generators set.

@knipknap

Copy link
Copy Markdown
Contributor

(I will draft a PR for phase 1, as that was a pre-existing mess so I will clean this up.)

@switch87

Copy link
Copy Markdown
Contributor Author

Thanks for testing it this thoroughly and for the detailed plan. I agree on all three points, and the workpiece filter is a much cleaner base than generated_workpiece_uid.

I'll leave phase 1 to you then. Meanwhile I'll write the phase 2 plan, refactoring the Material Test Grid generator so the three tests share one architecture and one UX (dedicated layer, dialog opens immediately and doesn't hide the test, per-cell editable result), and post it here for review before writing any code. I've turned this PR back into a draft so it doesn't sit in your review queue; phase 3 will rebuild these generators on top of the cleaned-up code, including the screenshot targets for the docs.

@switch87
switch87 marked this pull request as draft October 15, 2026 10:33
@switch87

Copy link
Copy Markdown
Contributor Author

Phase 2 plan: one generator architecture for all calibration tests

This plan starts after your phase 1 (Step.workpiece_uids / selected_workpieces()) is merged. Every step below links a step to its workpieces through that filter only. The pipeline never learns about "tests" or "generated" content.

Where things stand today

  • Material Test Grid. MaterialTestCmd.create_test_grid() adds one MaterialTestStep and one procedural preview workpiece to the active layer. The cells, labels, risk ordering and Speed-vs-Offset power scaling are all computed inside the raygeo material_test_grid assembler, so one step holds every cell. The test settings live on the step and are edited in the step settings dialog. sync_preview_from_step resizes the preview when they change. The asset is added with doc.add_asset() outside the undo transaction, so it stays behind after an undo. MaterialTestCmd has no tests.
  • PR feat: add Interval Test and Focus Test generators to the Tools menu #539. It creates its own layer, with one standard Engrave/Contour step and one workpiece per cell, a shared labels workpiece and step, and Command steps between Focus Test lines. Its parameters exist only in a create dialog, so a test can't be changed after it is created. Workpieces go through an SVG export and import with a regex fill hack. Centering is done by hand. The layer gets the default color and no stock material.

What all three tests share: a parameter VarSet, a row or grid layout with labels, an ordered list of "cell = workpiece + step" entries (sometimes with commands in between), placement on the work area, one undo entry, and regeneration when a parameter changes.

Target shape (all in laser_essentials, nothing new in core)

  • calibration/generator.py: CalibrationGenerator base class with id, title, varset(context), defaults() and layout(params, context) -> CalibrationLayout. It is pure: no document, no GTK.
  • calibration/layout.py: CalibrationLayout is an ordered list of CellEntry(name, geometry or workpiece factory, step factory) and CommandEntry(name, lines). Order in the list is execution order. Shared helpers: row_positions, grid_positions and label_geometry. The text helper from feat: add Interval Test and Focus Test generators to the Tools menu #539 moves here. All geometry is in test-local mm, Y up, origin at the bottom-left of the test.
  • calibration/builder.py: CalibrationBuilder turns a layout into document content. It creates the layer (unique name, unused color, default stock material, the same as AddLayerAndSetActiveCommand._create_default_layer), the assets, workpieces and steps (step.set_workpiece_uids([wp.uid])) and the commands. Everything runs as undoable commands in one transaction. Placement is converted from test-local to world coordinates in exactly one function.
  • calibration/record.py: CalibrationRecord, a small asset type registered through register_asset_types. It stores the generator id, a format version, the params and the uids of the items it generated. It is saved in the .ryp like any asset.
  • commands/calibration_cmd.py: CalibrationCmd (editor.calibration) with create(generator_id) and update_params(record, params). update_params regenerates the test in one undo entry, anchored at the test's current position, so a moved test stays where the user put it.
  • widgets/calibration_provider.py: CalibrationPropertyProvider, the test settings shown in the right panel (see 2.6).

Steps

Each step is one small PR with its own CHANGELOG.md line. New strings go in _(), and no .po/.pot changes are included. Lint runs first (pixi run lint), then the targeted tests.

2.1 Characterization tests for the Material Test Grid (tests only)

  • New rayforge-addon-laser/tests/commands/test_material_test_cmd.py. It covers:
    • create_test_grid() adds one step and one workpiece, linked through workpiece_uids, centered on axis_extents.
    • One undo removes both.
    • Changing grid_dimensions resizes the preview.
  • A golden test on the assembler output for each grid mode: the (speed, power, passes, offset) of every cell and the order they run in, for the default 5×5. 2.7 uses this golden to prove equivalence.
  • Users see: nothing.

2.2 Extract the generator interface (refactor, no behaviour change)

  • Add calibration/generator.py and calibration/layout.py.
  • MaterialTestGridGenerator (calibration/material_test_grid.py) returns a layout with a single entry: the procedural preview workpiece plus a configured MaterialTestStep.
  • MaterialTestStep.recipe_varset() stays as it is because recipes depend on it ("MATERIAL_TEST" in core/recipe.py). The generator reuses it.
  • Users see: nothing. 2.1 stays green, plus unit tests for the pure layout.

2.3 Shared builder and one-transaction undo

  • Add calibration/builder.py. MaterialTestCmd.create_test_grid() becomes a thin call into the builder, still targeting the active layer in this step.
  • Assets are added through ListItemCommand(..., "add_asset") inside the transaction. This fixes the asset left behind after an undo, and it is the only intended change. 2.1 gets a test for it.
  • Users see: undo now cleans up fully. Nothing else changes.

2.4 Dedicated layer

  • The builder creates a new layer named after the test, with the step in that layer's workflow, and makes it active. Undo removes the layer, its content and the asset.
  • Users see: the Material Test Grid gets its own layer, like the tests in feat: add Interval Test and Focus Test generators to the Tools menu #539. Tests: layer count and contents, undo and redo, and that the original active layer is restored on undo.

2.5 Persisted parameters and regeneration

  • Add CalibrationRecord and CalibrationCmd.update_params(). Regeneration replaces the items listed in the record in one undo entry, at the test's current anchor.
  • For the Material Test Grid, edits on the step settings page go through update_params instead of the descendant_updated → sync_preview_from_step path, which is removed.
  • Users see: nothing new, but a changed test survives save and reload with its parameters. Tests: record round-trip through to_dict/from_dict, regeneration keeps position, one undo entry per change.

2.6 Test settings in the right panel (the "dialog" opens immediately and never covers the test)

  • CalibrationPropertyProvider is registered in property_provider_registry. It shows a "Test Settings" group whenever the selection contains workpieces of a calibration record, built with VarSetRowList. Changes are debounced the same way SketchPropertyProvider does it (300 ms).
  • After Tools → Create … the builder selects the new test, so the panel appears straight away in the right panel and the canvas and test stay fully visible. Selecting any cell later brings it back. Removing the test is a plain undo or a layer delete. There is no Create/Cancel window.
  • I prefer this to a dialog window because GTK4 can't position windows on Wayland, so any dialog can end up on top of the test again.
  • The Material Test Grid's Preset, Grid, Labels and Parameters sections move from MaterialTestGridSettingsPage to the panel. The step dialog keeps Laser and Post Processing.
  • Users see: all tests behave the same. Tests: provider can_handle, values to update_params, panel shown after creation (uitest).

2.7 Per-cell editable Material Test Grid (only if you agree, see question 1)

  • MaterialTestGridGenerator.layout() emits:
    • one standard Engrave or Contour step per cell, with power, speed, MultiPassTransformer passes and bidir_x_offset_mm, in the assembler's risk order;
    • one labels workpiece with one low-power step.
  • MaterialTestStep stays registered (hidden) so older projects load and run unchanged.
  • Users see: every cell can be adjusted in its own step settings. Tests: the 2.1 golden matches the per-cell steps (values and order).
  • Docs: material-test-grid.md. Screenshotter: scripts/screenshot/material_test.py captures the panel instead of the step dialog.

Phase 3 (on top of the above, replacing the code in #539)

  1. Interval Test. calibration/interval_test.py as a generator (pure layout and VarSet from feat: add Interval Test and Focus Test generators to the Tools menu #539, reusing the shared row and label helpers), a Tools menu entry, tests, docs, and the screenshot target scripts/screenshot/interval_test.py registered in cli.py.
  2. Focus Test. calibration/focus_test.py. Z axis, manual and ramp modes, with CommandEntry items between the lines, and modes offered depending on the driver and the Automation addon. Tests, docs, and screenshot targets for the panel in each mode.

#539 stays a draft for reference and gets closed when 3.1 is opened.

Open questions

  1. Per-cell Material Test Grid: at the 20×20 maximum that is 400 steps in one workflow. Do you want 2.7 like this, a lower grid limit, or should the grid stay one assembler step (own layer and panel, but no per-cell editing)?
  2. Where to keep the parameters: a CalibrationRecord asset as proposed, or something on Layer (extra or a field)?
  3. Regeneration vs. hand edits: when the test parameters change, should regeneration overwrite cells the user edited by hand (proposed, with a note in the panel), or should the parameters lock once a cell has been edited?
  4. Placement: keep centering on axis_extents as today, or use machine.work_area so work margins are respected?
  5. Labels in per-cell mode: fine to build them in Python with text_to_geometry, or should the label layout become a raygeo helper shared with the assembler?
  6. Settings in the right panel instead of a dialog window (2.6): is that what you had in mind with "dialog opens immediately", or do you prefer a dialog that is placed so it doesn't cover the test?

The values every cell or line of the two new calibration tests is made
with: evenly spread line intervals (with their LPI) and focus offsets,
the row layout, and the commands the Focus Test puts between its
lines: an M0 pause in manual mode, or relative Z moves (G91/G0 Z/G90)
that end at the starting height, honouring Reverse Z. Offsets are
limited to +/-10 mm.

Refs barebaric#519
CalibrationTestCmd creates each test as its own layer. Every cell or
line is a vector workpiece with its own step, linked through
generated_workpiece_uid like the Material Test Grid, so it stays
editable. Interval cells are Engrave steps with constant power and
their own line interval; focus lines are Contour steps, with Command
steps in between for the manual and Z axis modes. Labels share one
workpiece and one low-power contour step that runs first. The test is
centered on the work area and added in one undoable step.

Golden G-code tests cover the interval pitch per cell, the M0 pauses
(always after M5) and the balanced Z moves.

Refs barebaric#519
Each entry opens a dialog for the settings, with unit-aware lengths
and speeds capped at the machine's maximum. The Focus Test dialog only
offers the methods the machine supports and explains the procedure
for the chosen one.

Refs barebaric#519
The method list is already ordered by preference (Z axis steps, manual,
ramp) and only holds what the machine supports, so the dialog now
starts on its first entry instead of the manual method.

Refs barebaric#519
@switch87
switch87 marked this pull request as ready for review October 17, 2026 14:06
@github-actions

Copy link
Copy Markdown

This pull request was closed automatically because its description does not follow the pull request template. Problems found:

  • section "What does this PR change?" is missing
  • section "Why did you choose this specific architectural approach?" is missing
  • section "How did you manually test this locally?" is missing
  • section "Checklist" is missing

Please edit the pull request description to answer every section and to tick every checkbox; as soon as it complies, the pull request is reopened automatically. See CONTRIBUTING.md.

@github-actions github-actions Bot closed this Oct 17, 2026
@github-actions github-actions Bot added the invalid-template Does not follow the pull request template label Oct 17, 2026
@knipknap

Copy link
Copy Markdown
Contributor

Sorry, bot went wild. Tried to reopen and that is also blocked... ugh. Checking what can be done.

@knipknap knipknap reopened this Oct 17, 2026
@github-actions github-actions Bot closed this Oct 17, 2026
@knipknap knipknap reopened this Oct 17, 2026
@knipknap knipknap removed the invalid-template Does not follow the pull request template label Oct 17, 2026
The Material Test Grid, the Interval Test and the Focus Test now share
one architecture and behave the same:

- A CalibrationGenerator turns its parameters into a layout: an
  ordered list of cells (one shape plus the step that processes only
  that shape) and machine commands between them. Generators do not
  touch the document.
- CalibrationBuilder turns a layout into workpieces, steps and source
  assets; CalibrationCmd adds them on a new layer named after the test
  and rebuilds them when a parameter changes, each in one undo step.
  A rebuilt test keeps its place and stays on the work area. Steps the
  user added to the layer stay.
- A hidden CalibrationRecord asset keeps the generator, its
  parameters and the uids of what it made, so the test can be rebuilt
  after saving and loading. A Material Test Grid from an older project
  gets a record on its first change.
- Tools -> Create ... adds the test with default settings and selects
  it. Its settings show under Test Settings in the item properties
  panel instead of a modal dialog, so the test stays in view.
- The Material Test Grid's grid, label and range settings moved from
  its step settings to Test Settings, with the presets, the
  mode-dependent row titles, the Speed vs Offset defaults and the
  machine speed limit. Its G-code is unchanged.

Refs barebaric#519
The material-test target now creates the grid through Tools -> Create
Material Test Grid and captures the main window with the Test Settings
in the item properties panel, instead of the step settings dialog. New
interval-test and focus-test targets do the same for the other two
tests, and the Workpieces tab target creates the grid through the new
command.

Refs barebaric#519
The Interval and Focus Test entry sat under 1.13.0-beta1, which is
already released. The changelog is written for each release.

Refs barebaric#519
All three calibration tests are created on their own layer and set up
under Test Settings in the item properties panel, with every change
rebuilt as one undo step. Update the Material Test Grid and the
Interval and Focus Tests pages, and their translations, to match.

Refs barebaric#519
Workflow.add_children and remove_children went straight to DocItem and
skipped the per-step transformer wiring that add_child and set_children
do, so a step added in bulk did not report post-processor changes.
Doc.remove_asset_by_uid gets a silent flag, like add_asset, for
commands that change several assets and notify once.

Refs barebaric#519
Rebuilding a test replaced its workpieces, steps and assets with one
command per item. Every item made the layers panel and the workflow
views rebuild, so a Focus Test with Z axis steps froze the window for
about three seconds after each change. RebuildCalibrationCommand now
swaps them in bulk with the pipeline paused, as grouping does, which
takes under 0.1 s. Cells in a group go back to it on undo.

Refs barebaric#519
@switch87

Copy link
Copy Markdown
Contributor Author

@knipknap A short reminder about the phase 2 plan above. The six open questions are still open.

In the meantime I built it locally, following the plan, with these answers:

  1. The grid stays one assembler step, on its own layer and with the settings in the panel. No per-cell steps, so no 400 steps.
  2. The parameters live in a hidden CalibrationRecord asset.
  3. A change of the parameters rebuilds the test and overwrites hand edits to the generated steps. The panel says so.
  4. Centering on axis_extents, as today.
  5. Labels stay as they are.
  6. Settings in the right panel, no window.

All three tests now behave the same and share one generator. The Material Test Grid gives the same G-code as before, and old projects with a grid still load and run.

Before I push anything: do you agree with these answers? And do you want it as the small PRs from the plan (2.1 to 2.6, then 3.1 and 3.2), or as one update of this PR? The plan promised small PRs, so that is what I'll do unless you prefer otherwise.

A recipe applied to the Material Test Grid's step changed its grid
settings, but the preview kept its size, the Test Settings kept the
old values and the next change there dropped the recipe. A rebuild
also replaced the step, so the laser head and the post-processing set
in its own settings were lost.

- A generator can now keep its steps on a rebuild and change only the
  settings it controls. The grid does, and reads its settings back
  from the step, where recipes apply them.
- CalibrationSync, registered as an editor command, redraws the grid
  preview when its step's grid settings change, as before the grid
  got its own layer.
- The same command takes a test's record and hidden source assets out
  of the document with the test's layer and puts them back when the
  layer returns, so a deleted test leaves nothing behind in the saved
  project and undo still works.
- The Test Settings provider is registered in register_commands
  instead of on import, so importing the frontend does not add it
  twice.

Refs barebaric#519
A Focus Test made with Z axis steps showed Manual on a machine without
a Z axis, and any other change rebuilt it as Manual. The Test Settings
now apply only the settings that were changed, list the test's own
method even when the machine lacks it, and say that it is not
available there.

The panel no longer waits on timeouts: a rebuilt test is selected
again right away, and a new test's settings are scrolled into view
each time the panel's size changes until a setting is edited or
another test is selected.

Refs barebaric#519
Deleting a test's layer took all of its source assets out of the
document, also the one of a cell the user had moved to another layer,
which then lost its source. The layer sync now uses the same in-use
check as a rebuild.

Refs barebaric#519
After a new test was deselected, or another item was selected, the
panel kept scrolling to where the Test Settings card had been whenever
its size changed. Following now stops as soon as the card is hidden.

Refs barebaric#519
Like the sketcher's parameters, the Test Settings now have their own
register function. The frontend calls it from rayforge_init instead of
register_commands. It still does not run on import, so a module
imported under two names does not add a second panel.

Refs barebaric#519
A value that made a test invalid stayed in its row but was dropped from
the next rebuild, so fixing another setting quietly went back to the
old value. It now stays pending until a rebuild takes it or another
test is selected.

Refs barebaric#519
The Workpieces tab target looked the grid's step up by its index in
its layer, but opened the step at that index across all layers. With
the grid on its own layer that was another step. It also moves one
workpiece onto the grid's layer, so the tab shows a workpiece the
grid's step leaves out, as the docs describe.

Refs barebaric#519
Regenerate material-test and the Material Test Grid's Workpieces tab,
and add interval-test and focus-test to the Interval and Focus Tests
page in every language.

Refs barebaric#519
@switch87

Copy link
Copy Markdown
Contributor Author

I pushed the phase 2 work to this PR after all (up to a274314), so you can look at it and CI runs on it. The description is updated.

If you'd rather review it as the small PRs from the plan, say so and I'll split it; I already know which files and tests belong to which step.

This branch was previously deployed

1 inactive (outdated) deployment
pypi — 6b01844a Deployed Oct 17, 2026 by knipknap via Build and Publish Wheel #2366
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Focus Test: default the Method to "Z axis steps" when the machine has a Z axis Interval Test and Focus Test generators

3 participants